/////////////////////////////////////////////////////////////////////////////////////
// Include Files
#include "Managers\MeshManager.h"

/////////////////////////////////////////////////////////////////////////////////////
// ISingleton<CMeshManager> Static Initialization
CMeshManager *ISingleton<CMeshManager>::m_pInstance = CE_NULL;

// INLINE GALLEON MESH FILE HACK
#include "..\Galleons\Meshes\Galleon.mesh"

/* TODO
 * models are instanced into the scene, textures, shaders, and animations are associated with each instance
 * any number of textures can be attached with no limitations
 * only one shader can be attached, and it must have a matching vertex declaration
 * any number of animations may be attached:
 *     for skeletal animation, you must link up the bones of the animation to the bones of the model
 *     for morphing animation, you must link up the vertices of the animation to the vertices of the model
 * if the shader uses more textures than are attached, a checkerboard is used for the remainder (issue warning)
 * if the shader uses less textures than are attached, the remainder are ignored (issue warning)
 *
 *
 *
 * vertex types (in directx style?), (from flux), these are used in meshes and shaders...
 *
 * static F3DVertexElement RenderVertexElem[] = {
 *     { 0, offsetof(RenderVertex, x),		F3DDECLTYPE_FLOAT3,		F3DDECLMETHOD_DEFAULT,	F3DDECLUSAGE_POSITION,	0 },
 *     { 0, offsetof(RenderVertex, u),		F3DDECLTYPE_FLOAT2,		F3DDECLMETHOD_DEFAULT,	F3DDECLUSAGE_TEXCOORD,	0 },
 *     { 0, offsetof(RenderVertex, color),	F3DDECLTYPE_UBYTE4N,	F3DDECLMETHOD_DEFAULT,	F3DDECLUSAGE_COLOR,		0 },
 *     F3DDECL_END() // == { 0xFF, 0, F3DDECLTYPE_UNUSED, 0, 0, 0 }
 * };
 *
 * { stream_number, element_offset, data_type, tessellation_method, usage, usage_index }
 *
 * my version, modified from the one above:
 * removed stream_number for now, but it could be useful when optimizing, for example for water only the position changes, it can be kept in a separate stream
 * removed tessellation_method, I'll get into that when I'm ready for tessellation...
 * CE_DECLUSAGE needs to have a straight correspondence to the 16 inputs, also include CE_DECLUSAGE_0 - CE_DECLUSAGE_15
 * usage_index... ???... took it out
 *
 * VertexDecl RenderVertexDecl(3);
 * RenderVertexDecl[0] = VertexElem( 0,  CE_DECLTYPE_FLOAT3, CE_DECLUSAGE_POSITION );
 * RenderVertexDecl[1] = VertexElem( 12, CE_DECLTYPE_FLOAT2, CE_DECLUSAGE_TEXCOORD );
 * RenderVertexDecl[2] = VertexElem( 20, CE_DECLTYPE_BYTE4N, CE_DECLUSAGE_COLOR    ); <--- BYTE4N: 4 bytes are normalized by dividing by 255
 */

enum decltype_t
{
	DECLTYPE_FLOAT1, 
	DECLTYPE_FLOAT2, 
	DECLTYPE_FLOAT3, 
	DECLTYPE_FLOAT4, 

	DECLTYPE_FLOAT1N, 
	DECLTYPE_FLOAT2N, 
	DECLTYPE_FLOAT3N, 
	DECLTYPE_FLOAT4N, 
};

enum declusage_t
{
	DECLUSAGE_0, 
	DECLUSAGE_1, 
	DECLUSAGE_2, 
	DECLUSAGE_3, 
	DECLUSAGE_4, 
	DECLUSAGE_5, 
	DECLUSAGE_6, 
	DECLUSAGE_7, 
	DECLUSAGE_8, 
	DECLUSAGE_9, 
	DECLUSAGE_10, 
	DECLUSAGE_11, 
	DECLUSAGE_12, 
	DECLUSAGE_13, 
	DECLUSAGE_14, 
	DECLUSAGE_15, 

	DECLUSAGE_POSITION  = DECLUSAGE_0, 
	DECLUSAGE_NORMAL    = DECLUSAGE_1, 
	DECLUSAGE_AMBIENT   = DECLUSAGE_2, 
	DECLUSAGE_DIFFUSE   = DECLUSAGE_3, 
	DECLUSAGE_SPECULAR  = DECLUSAGE_4, 
	DECLUSAGE_MISC0     = DECLUSAGE_5, 
	DECLUSAGE_MISC1     = DECLUSAGE_6, 
	DECLUSAGE_MISC2     = DECLUSAGE_7, 
	DECLUSAGE_TEXCOORD0 = DECLUSAGE_8, 
	DECLUSAGE_TEXCOORD1 = DECLUSAGE_9, 
	DECLUSAGE_TEXCOORD2 = DECLUSAGE_10, 
	DECLUSAGE_TEXCOORD3 = DECLUSAGE_11, 
	DECLUSAGE_TEXCOORD4 = DECLUSAGE_12, 
	DECLUSAGE_TEXCOORD5 = DECLUSAGE_13, 
	DECLUSAGE_TEXCOORD6 = DECLUSAGE_14, 
	DECLUSAGE_TEXCOORD7 = DECLUSAGE_15, 
};

/////////////////////////////////////////////////////////////////////////////////////
// CMeshManager Implementation

void CMeshManager::Initialize()
{
}

void CMeshManager::Shutdown()
{
}

void CMeshManager::LoadMeshFile( string_t _strFilename )
{
	MeshLoad.Raise( new CMeshEventArg(_strFilename) );
}

void CMeshManager::CreateMesh( CMeshEventArg &_rArg )
{
	string_t _strFilename = _rArg.GetName();

	TMesh mesh;
	mesh.strName = _strFilename;

	glGenVertexArrays(1, &mesh.uiVAO);
	glBindVertexArray(mesh.uiVAO);

	// HACK HACK OMG HACK SO MUCH HACK IT BURNS OH FUCK IT HURTS
	if( _strFilename == L"Galleon.mesh" )
	{
		/* all the information below needs to go in a file:
		 * 
		 * index buffer:
		 *     uint32 - index data type
		 *     uint32 - index count
		 *     uint32 - usage
		 *     byte[] - index buffer
		 * 
		 * vertex buffer:
		 *     uint32 - number of bytes in vertex buffer
		 *     uint32 - usage
		 *     byte[] - vertex buffer
		 * 
		 * vertex attributes:
		 *     uint32 - number of vertex attributes
		 *     attribute[]
		 *         uint32 - index
		 *         uint32 - size
		 *         uint32 - type
		 *         uint32 - normalized
		 *         uint32 - stride
		 *         uint32 - offset
		 */

		// vertex buffer
		mesh.uiVBO = CreateBuffer( GL_ARRAY_BUFFER, 
			g_uiVertexCount * sizeof(float_t), g_pVertexData, GL_STATIC_DRAW );

		// index buffer
		mesh.uiIBO = CreateBuffer( GL_ELEMENT_ARRAY_BUFFER, 
			g_uiIndexCount * sizeof(uint16_t), g_pIndexData, GL_STATIC_DRAW );

		// vertex attributes
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 36, (void*)0);
		glEnableVertexAttribArray(0);

		glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 36, (void*)12);
		glEnableVertexAttribArray(1);

		glVertexAttribPointer(2, 3, GL_FLOAT, GL_TRUE, 36, (void*)24);
		glEnableVertexAttribArray(2);

		// drawing data
		mesh.uiIndexCount  = g_uiIndexCount;
		mesh.eIndexType    = GL_UNSIGNED_SHORT;
	}
	else if( _strFilename == L"Sky.mesh" )
	{
		float_t arrVertices[8][3];
		arrVertices[0][0] =  1.00f; arrVertices[0][1] =  1.00f; arrVertices[0][2] = -1.00f;
		arrVertices[1][0] = -1.00f; arrVertices[1][1] =  1.00f; arrVertices[1][2] =  1.00f;
		arrVertices[2][0] = -1.00f; arrVertices[2][1] =  1.00f; arrVertices[2][2] = -1.00f;
		arrVertices[3][0] =  1.00f; arrVertices[3][1] =  1.00f; arrVertices[3][2] =  1.00f;
		arrVertices[4][0] =  1.00f; arrVertices[4][1] = -1.00f; arrVertices[4][2] =  1.00f;
		arrVertices[5][0] = -1.00f; arrVertices[5][1] = -1.00f; arrVertices[5][2] = -1.00f;
		arrVertices[6][0] = -1.00f; arrVertices[6][1] = -1.00f; arrVertices[6][2] =  1.00f;
		arrVertices[7][0] =  1.00f; arrVertices[7][1] = -1.00f; arrVertices[7][2] = -1.00f;

		uint8_t arrIndices[36];
		arrIndices[ 0] = 0; arrIndices[ 1] = 2; arrIndices[ 2] = 1;
		arrIndices[ 3] = 3; arrIndices[ 4] = 0; arrIndices[ 5] = 1;
		arrIndices[ 6] = 4; arrIndices[ 7] = 6; arrIndices[ 8] = 5;
		arrIndices[ 9] = 7; arrIndices[10] = 4; arrIndices[11] = 5;
		arrIndices[12] = 3; arrIndices[13] = 1; arrIndices[14] = 6;
		arrIndices[15] = 4; arrIndices[16] = 3; arrIndices[17] = 6;
		arrIndices[18] = 7; arrIndices[19] = 5; arrIndices[20] = 2;
		arrIndices[21] = 0; arrIndices[22] = 7; arrIndices[23] = 2;
		arrIndices[24] = 1; arrIndices[25] = 2; arrIndices[26] = 5;
		arrIndices[27] = 6; arrIndices[28] = 1; arrIndices[29] = 5;
		arrIndices[30] = 0; arrIndices[31] = 3; arrIndices[32] = 4;
		arrIndices[33] = 7; arrIndices[34] = 0; arrIndices[35] = 4;

		// vertex buffer
		mesh.uiVBO = CreateBuffer( GL_ARRAY_BUFFER, 
			8 * 3 * sizeof(float_t), arrVertices, GL_STATIC_DRAW );
		// = uiBuffer;

		// index buffer
		mesh.uiIBO = CreateBuffer( GL_ELEMENT_ARRAY_BUFFER, 
			36 * sizeof(uint8_t), arrIndices, GL_STATIC_DRAW );

		// vertex attributes
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, (void*)0);
		glEnableVertexAttribArray(0);

		// drawing data
		mesh.uiIndexCount = 36;
		mesh.eIndexType = GL_UNSIGNED_BYTE;
	}
	else if( _strFilename == L"Water.mesh" )
	{
		struct Vert
		{
			float_t pos[3];
			float_t tex0[2];
			float_t tex1[2];
		};

		Vert arrVertices[101][101];
		for( int_t x = 0; x < 101; ++x )
		{
			for( int_t z = 0; z < 101; ++z )
			{
				float_t fX = (50.0f - x) * 10.0f;
				float_t fZ = (50.0f - z) * 10.0f;

				arrVertices[x][z].pos[0] = fX;
				arrVertices[x][z].pos[1] = 0.0f;
				arrVertices[x][z].pos[2] = fZ;

				arrVertices[x][z].tex0[0] = fX / 200.0f;
				arrVertices[x][z].tex0[1] = fZ / 200.0f;

				arrVertices[x][z].tex1[0] = fX / 50.0f;
				arrVertices[x][z].tex1[1] = fZ / 50.0f;
			}
		}

		uint32_t arrIndices[60000];
		uint32_t uiVert = 0;
		for( uint32_t x = 0; x < 100; ++x )
		{
			for( uint32_t z = 0; z < 100; ++z )
			{
				arrIndices[uiVert++] = (x + 0) * 101 + (z + 0);
				arrIndices[uiVert++] = (x + 1) * 101 + (z + 0);
				arrIndices[uiVert++] = (x + 1) * 101 + (z + 1);
				arrIndices[uiVert++] = (x + 0) * 101 + (z + 0);
				arrIndices[uiVert++] = (x + 1) * 101 + (z + 1);
				arrIndices[uiVert++] = (x + 0) * 101 + (z + 1);
			}
		}

		// vertex buffer
		mesh.uiVBO = CreateBuffer( 
			GL_ARRAY_BUFFER, 10201 * sizeof(Vert), 
			arrVertices, GL_DYNAMIC_DRAW 
			);

		// index buffer
		mesh.uiIBO = CreateBuffer( 
			GL_ELEMENT_ARRAY_BUFFER, 60000 * sizeof(uint32_t), 
			arrIndices, GL_STATIC_DRAW 
			);

		// vertex attributes
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vert), (void*)0);
		glEnableVertexAttribArray(0);

		glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vert), (void*)12);
		glEnableVertexAttribArray(1);

		glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vert), (void*)20);
		glEnableVertexAttribArray(2);

		// drawing data
		mesh.uiIndexCount = 60000;
		mesh.eIndexType = GL_UNSIGNED_INT;
	}
	else
	{
		assert(false);
	}
	m_lstMeshes.Append(mesh);
}

void CMeshManager::UpdateMesh( CMeshEventArg &_rArg )
{
	// get the mesh
	TMesh *pMesh = GetMesh(_rArg.GetName());

	// TODO: currently this is depending on the fact that the water is the only 
	//     mesh that updates and that it will always be updating its vertex buffer

	// update the vertex buffer
	glBindBuffer( GL_ARRAY_BUFFER, pMesh->uiVBO );
	void *pMap = glMapBuffer( GL_ARRAY_BUFFER, GL_WRITE_ONLY );
	memcpy(pMap, _rArg.GetVertexData(), _rArg.GetVertexDataSize());
	glUnmapBuffer(GL_ARRAY_BUFFER);
}

uint_t CMeshManager::CreateBuffer( GLenum _eTarget, uint_t _uiSize, void *_pData, GLenum _eUsage )
{
	// create the buffer
	uint_t uiBuffer;
	glGenBuffers( 1, &uiBuffer );
	glBindBuffer( _eTarget, uiBuffer );
	glBufferData( _eTarget, _uiSize, _pData, _eUsage );
	return uiBuffer;
}
